/* 
 * Copyright (c) 2012, Fromentin Xavier, Schnell Michaël, Dervin Cyrielle, Brabant Quentin
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *      * The names of its contributors may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Fromentin Xavier, Schnell Michaël, Dervin Cyrielle OR Brabant Quentin 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package kameleon.gui.view;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

import kameleon.exception.FileReadingException;
import kameleon.exception.KameleonException;
import kameleon.gui.exception.InvalidLookAndFeelException;
import kameleon.gui.exception.UnknownKeyException;
import kameleon.gui.language.GuiLanguage;
import kameleon.gui.language.SwitchLanguage;
import kameleon.gui.model.Model;
import kameleon.gui.model.Observer;
import kameleon.gui.util.FileConstants;
import kameleon.gui.util.ImageUtility;
import kameleon.gui.util.LanguageConstants;

/**
 * Menu bar of the Kameleon graphical interface.
 * 
 * <p>The menu contains the following entries: 
 * <ul>
 * <li>settings
 * <li>plug-ins
 * </ul>
 * 
 * <p>The settings menu contains the following entries: 
 * <ul>
 * <li>sub menu language
 * <li>sub menu look and feel
 * </ul>
 * 
 * <p>The language sub-menu contains the following entries: 
 * <ul>
 * <li>French
 * <li>English
 * </ul>
 * Each entry is displayed using the country flag and the language
 * name. This menu is used to change the display language of the
 * graphical interface.
 * 
 * <p>The look and feel contains a entry for each supported look
 * and feel (displayed using the look and feel 
 * {@link LookAndFeelInfo#getName() name}). This menu is used to
 * change the look and feel used by the graphical interface.
 * 
 * <p>The plug-ins menu contains the following entries: 
 * <ul>
 * <li>delete an analyzer
 * <li>delete a generator
 * </ul>
 * 
 * @author		Fromentin Xavier, Schnel Michaël
 * @version		1.0
 */
public class Menu extends JMenuBar 
implements Observer, LanguageConstants, FileConstants {

	/**
	 * Needed to serialize this class.
	 * 
	 * @see		java.io.Serializable
	 */
	private static final long serialVersionUID = -8853187770406547093L ;

	/**
	 * Model for this view.
	 */
	private Model model ;
	
	/**
	 * {@code JFrame} using this menu.
	 */
	private JFrame container ;

	/**
	 * Settings menu.
	 */
	private JMenu settings ;
	
	/**
	 * Languages sub-menu.
	 */
	private JMenu languages ;
	
	/**
	 * French language menu item.
	 */
	private JMenuItem frenchLang ;
	
	/**
	 * English language menu item.
	 */
	private JMenuItem englishLang ;
	
	/**
	 * Look and feel sub-menu.
	 */
	private JMenu lookAndFeel ;
	
	/**
	 * Plug-ins menu.
	 */
	private JMenu plugIns ;

	/**
	 * Menu item used to delete an analyzer.
	 */
	private JMenuItem deleteAnalyzer ;

	/**
	 * Menu item used to delete a generator.
	 */
	private JMenuItem deleteGenerator ;

	/**
	 * Builds an instance with the given model and container.
	 * 
	 * @param	model
	 * 			model for this view
	 * 
	 * @param	container
	 * 			component containing this menu
	 * 
	 * @throws 	KameleonException 
	 * 			if the menu could not be built
	 */
	public Menu(Model model, JFrame container) 
			throws KameleonException {
		super() ;
		this.model = model ;
		this.container = container ;
		this.model.addObserver(this) ;

		this.build() ;
		this.update() ;
		this.reloadLanguage() ;

		this.setVisible(true) ;
	}// Menu(Model, JFrame)
	
	/**
	 * Updates the menu. Disables the menu item the current look and 
	 * feel and enables the previously disabled menu items.
	 */
	@Override
	public void update() {
		//TODO Render the selected look and field un-clickable
		this.deleteAnalyzer.setEnabled(
				!this.model.getKnownAnalyzers().isEmpty()) ;
		this.deleteGenerator.setEnabled(
				!this.model.getKnownGenerators().isEmpty()) ;
	}// update()

	/**
	 * Updates the displayed text for this menu and disables the menu 
	 * item for the current display language.
	 * 
	 * @throws 	UnknownKeyException
	 * 			if the new language file doesn't support a needed key
	 */
	@Override
	public void reloadLanguage() throws UnknownKeyException {
		SwitchLanguage sl = SwitchLanguage.getInstance() ;
		
		// Disable only the menu item of the current language
		//TODO Add access to the previously selected language to boost efficiency
		GuiLanguage currentLang = sl.getCurrentLanguage() ;
		this.frenchLang.setEnabled(
				!GuiLanguage.FRENCH.equals(currentLang)) ;
		this.englishLang.setEnabled(
				!GuiLanguage.ENGLISH.equals(currentLang)) ;

		// Update displaeyd text
		this.settings.setText(sl.getText(CONFIGURATION_MENU)) ;
		this.settings.setToolTipText(
				sl.getText(CONFIGURATION_MENU_TOOLTIP)) ;

		this.languages.setText(sl.getText(LANGUAGE_SELECTION_MENU)) ;
		this.languages.setToolTipText(
				sl.getText(LANGUAGE_SELECTION_MENU_TOOLTIP)) ;

		this.frenchLang.setText(
				sl.getText(LANGUAGE_SELECTION_MENU_FR)) ;
		
		this.englishLang.setText(
				sl.getText(LANGUAGE_SELECTION_MENU_EN)) ;

		this.lookAndFeel.setText(sl.getText(LOOK_AND_FEEL_MENU)) ;

		this.plugIns.setText(sl.getText(TOOLS_MENU)) ;
		this.plugIns.setToolTipText(sl.getText(TOOLS_MENU_TOOLTIP)) ;

		this.deleteAnalyzer.setText(sl.getText(DELETE_ANALYZER_MENU)) ;
		this.deleteAnalyzer.setToolTipText(
				sl.getText(DELETE_ANALYZER_MENU_TOOLTIP)) ;

		this.deleteGenerator.setText(sl.getText(DELETE_GENERATOR_MENU)) ;
		this.deleteGenerator.setToolTipText(
				sl.getText(DELETE_GENERATOR_MENU_TOOLTIP)) ;
	}// reloadLanguage()

	/**
	 * Builds the menu.
	 * 
	 * @throws 	FileReadingException
	 * 			if a menu icon could not be read
	 */
	private void build() throws FileReadingException {
		this.buildSettingsMenu() ;
		this.buildPlugInsMenu() ;
	}

	/**
	 * Builds the settings menu and adds it to this instance.
	 * 
	 * @throws	FileReadingException
	 * 			if a menu icon could not be read
	 */
	private void buildSettingsMenu() throws FileReadingException {
		// Menu Configuration
		this.settings = new JMenu() ;
		this.settings.setMnemonic(KeyEvent.VK_N) ;
		this.add(this.settings) ;

		// Sub-menu : language choice
		this.languages = new JMenu() ;
		this.languages.setMnemonic(KeyEvent.VK_L) ;
		this.settings.add(this.languages) ;

		// Language item: French
		this.frenchLang = this.buildLanguageItem(
				KeyEvent.VK_F, GuiLanguage.FRENCH) ;
		this.languages.add(this.frenchLang) ;

		// Language item: English
		this.englishLang = this.buildLanguageItem(
				KeyEvent.VK_E, GuiLanguage.ENGLISH) ;
		this.languages.add(this.englishLang) ;

		// Look and feel sub-menu
		this.lookAndFeel = new JMenu() ;
		this.lookAndFeel.setMnemonic(KeyEvent.VK_A) ;
		this.buildLookAndFeelItems() ;
		this.settings.add(this.lookAndFeel) ;
	}// buildSettingsMenu()

	/**
	 * Builds and returns a language menu item.
	 * 
	 * @param	mnemonic
	 * 			code of the mnemonic used by the built menu item
	 * 
	 * @param	targetLang
	 * 			language which will be set by clicking this menu item
	 * 
	 * @return	Language menu item with the given values
	 * 
	 * @throws	FileReadingException
	 * 			if the flag icon could not be read
	 */
	private JMenuItem buildLanguageItem(int mnemonic, 
			GuiLanguage targetLang) 
					throws FileReadingException {
		String filePath = null ;
		ImageIcon icon = null ;
		final Class<?> thisClass = this.getClass() ;

		// Retrieve flag icon
		filePath = String.format(
				GUI_FLAG_ICON_FILE,
				targetLang.getCountry().getCountry().toLowerCase()) ;
		InputStream src = thisClass.getResourceAsStream(filePath) ;
		File flagFile = new File(filePath) ;
		if (src == null) {
			throw new FileReadingException(flagFile) ;
		}// if
		icon = new ImageIcon(ImageUtility.getImageBytes(
				new BufferedInputStream(src))) ;
		try {
			src.close() ;
		} catch (IOException e) {
			throw new FileReadingException(flagFile) ;
		}// try
		
		// Build menu item
		JMenuItem item = new JMenuItem(icon) ;
		item.setMnemonic(mnemonic) ;
		item.addActionListener(new LanguageChangeListener(
				this.model, 
				targetLang)) ;

		return item ;
	}// buildLanguageItem(int, GuiLanguage)

	/**
	 * Builds all the look and feel menu items.
	 */
	private void buildLookAndFeelItems() {
		final LookAndFeelInfo[] lafs = 
				UIManager.getInstalledLookAndFeels() ;

		for(final LookAndFeelInfo lafInfo : lafs) {
			final JMenuItem item = new JMenuItem(lafInfo.getName()) ;
			this.lookAndFeel.add(item) ;
			item.addActionListener(new LookAndFeelChangeListener(
					lafInfo.getClassName(), 
					this.container, 
					this.model)) ;
		}// for
	}// buildLookAndFeelItems()
	
	/**
	 * Builds the plug-in menu and adds it to this instance.
	 */
	private void buildPlugInsMenu() {
		// Menu plug-ins
		this.plugIns = new JMenu() ;
		this.plugIns.setMnemonic(KeyEvent.VK_P) ;
		this.add(this.plugIns) ;

		// Menu item: delete analyzer
		this.deleteAnalyzer = new JMenuItem() ;
		this.deleteAnalyzer.setMnemonic(KeyEvent.VK_A) ;
		this.deleteAnalyzer.addActionListener(
				new DeleteAnalyzerListener(
						this.model, this.container)) ;
		this.plugIns.add(this.deleteAnalyzer) ;

		// Menu item: delete generator
		this.deleteGenerator = new JMenuItem() ;
		this.deleteGenerator.setMnemonic(KeyEvent.VK_G) ;
		this.deleteGenerator.addActionListener(
				new DeleteGeneratorListener(
						this.model, this.container)) ;
		this.plugIns.add(this.deleteGenerator) ;
		
	}// buildPlugInsMenu()

	/**
	 * Listener handling the clicks on language menu items. Tells the
	 * model to change the current language.
	 * 
	 * @author	Schnell Michaël
	 * @version	1.0
	 */
	private class LanguageChangeListener implements ActionListener {
		
		/**
		 * Model for this listener.
		 */
		private Model menuModel ;
		
		/**
		 * Target for the language switch for this listener.
		 */
		private GuiLanguage targetLanguage ;
		
		/**
		 * Builds an instance with the given values.
		 * 
		 * @param 	menuModel
		 * 			model for this listene
		 * 
		 * @param 	targetLanguage
		 * 			target for the language switch for this listener
		 */
		public LanguageChangeListener(Model menuModel, 
				GuiLanguage targetLanguage) {
			super() ;
			this.menuModel = menuModel ;
			this.targetLanguage = targetLanguage ;
		}// LanguageChangeListener(Model, GuiLanguage)
		
		/**
		 * Causes the model to change the current language.
		 * 
		 * @param	e
		 * 			event fetched by this listener
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			this.menuModel.changeLanguage(this.targetLanguage) ;
		}// actionPerformed(ActionEvent)
	
	}// class LanguageChangeListener

	/**
	 * Listener handling the clicks on look and feel menu items. Tells 
	 * the graphical interface to change the current look and feel.
	 * 
	 * @author	Schnell Michaël
	 * @version	1.0
	 */
	private class LookAndFeelChangeListener implements ActionListener {
		
		/**
		 * Target {@code JFrame} for the look and feel change.
		 */
		private JFrame menuContainer ;
		
		/**
		 * Model for this listener.
		 */
		private Model menuModel ;
		
		/**
		 * Complete class name of the target look and feel for the 
		 * change.
		 */
		private String targetLookAndFeelName ;
		
		/**
		 * Builds an instance with the given values.
		 * 
		 * @param	targetLookAndFeelName
		 * 			complete class name of the target look and feel 
		 * 			for the change
		 * 
		 * @param	menuContainer
		 * 			target {@code JFrame} for the look and feel change
		 * 
		 * @param	menuModel
		 * 			model for this listener
		 */
		public LookAndFeelChangeListener(String targetLookAndFeelName,
				JFrame menuContainer, Model menuModel) {
			super();
			this.targetLookAndFeelName = targetLookAndFeelName ;
			this.menuContainer = menuContainer ;
			this.menuModel = menuModel ;
		}// LookAndFeelChangeListener(String, JFrame, Model)
		
		/**
		 * Causes the container to change the current look and feel.
		 * 
		 * @param	e
		 * 			event fetched by this listener
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				UIManager.setLookAndFeel(this.targetLookAndFeelName) ;
				SwingUtilities.updateComponentTreeUI(
						this.menuContainer) ;
			} catch (Exception ex) {
				this.menuModel.displayDebugInformation(
						new InvalidLookAndFeelException(
								this.targetLookAndFeelName, ex)) ;
			}// try
		}// actionPerformed(ActionEvent)
		
	}// class LookAndFeelChangeListener
	
	/**
	 * Listener handling the clicks on the delete an analyzer menu item.
	 * 
	 * @author	Schnell Michaël
	 * @version	1.0
	 */
	private class DeleteAnalyzerListener implements ActionListener {
		
		/**
		 * Model for this listener.
		 */
		private Model menuModel ;
		
		/**
		 * {@code JFrame} containing the menu attached to this
		 * listener.
		 */
		private JFrame menuContainer ;
		
		/**
		 * Builds an instance with the given values.
		 * 
		 * @param	menuModel
		 * 			model for this listener
		 * 
		 * @param	menuContainer
		 * 			{@code JFrame} containing the menu attached to
		 * 			this listener
		 */
		public DeleteAnalyzerListener(Model menuModel, 
				JFrame menuContainer) {
			super() ;
			this.menuModel = menuModel ;
			this.menuContainer = menuContainer ;
		}// DeleteAnalyzerListener(Model, JFrame)
		
		/**
		 * Opens a new frame which allows the user to delete an analyzer.
		 * 
		 * @param	e
		 * 			event fetched by this listener
		 * 
		 * @see		DeleteAnalyzerFrame
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				new DeleteAnalyzerFrame(
						this.menuContainer, this.menuModel)
					.setVisible(true) ;
			} catch (KameleonException ke) {
				this.menuModel.displayDebugInformation(ke) ;
			}// try
		}// actonPerformed(ActionEvent)
		
	}// class DeleteAnalyzerListener
	
	/**
	 * Listener handling the clicks on the delete a generator menu item.
	 * 
	 * @author	Schnell Michaël
	 * @version	1.0
	 */
	private class DeleteGeneratorListener implements ActionListener {
		
		/**
		 * Model for this listener.
		 */
		private Model menuModel ;
		
		/**
		 * {@code JFrame} containing the menu attached to this
		 * listener.
		 */
		private JFrame menuContainer ;
		
		/**
		 * Builds an instance with the given values.
		 * 
		 * @param	menuModel
		 * 			model for this listener
		 * 
		 * @param	menuContainer
		 * 			{@code JFrame} containing the menu attached to
		 * 			this listener
		 */
		public DeleteGeneratorListener(Model menuModel, 
				JFrame menuContainer) {
			super() ;
			this.menuModel = menuModel ;
			this.menuContainer = menuContainer ;
		}// DeleteGeneratorListener(Model, JFrame)
		
		/**
		 * Opens a new frame which allows the user to delete a generator.
		 * 
		 * @param	e
		 * 			event fetched by this listener
		 * 
		 * @see		DeleteGeneratorFrame
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				new DeleteGeneratorFrame(
						this.menuContainer, this.menuModel)
				.setVisible(true) ;
			} catch (KameleonException ke) {
				this.menuModel.displayDebugInformation(ke) ;
			}// try
		}// actionPerformed(ActionEvent)
		
	}// class DeleteGeneratorListener
	
}// class Menu